package com.hofon.medical.dynamicdb;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.dbcp.BasicDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.ChildBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.io.support.ResourcePropertySource;

/**
 * db数据源变化，在运行时动态加载到spring容器，不必重启应用（使用到观察者模式）：通过读取配置文件，并进行正则表达式匹配获取DB信息
 * @author kongxiangyi
 *
 */
public class DynamicDataSourceListener implements ApplicationContextAware,
        ApplicationListener<DbChangedEvent> {

    private final Logger log = LoggerFactory
            .getLogger(DynamicDataSourceListener.class);

    private ApplicationContext app;

    @Override
    public void setApplicationContext(ApplicationContext app)
            throws BeansException {
        this.app = app;
    }

    public ApplicationContext getApplicationContext() {
        return app;
    }

    public void onApplicationEvent(DbChangedEvent event) {
        try {
            regDynamicBean(event);
        } catch (IOException e) {
            log.error("properties " + event.getFileName() + " read error!", e);
        }
        log.debug(event.getClass().getSimpleName() + " 事件已发生！");
    }

    private void regDynamicBean(DbChangedEvent event) throws IOException {
        // 解析属性文件，得到数据源Map
        Map<String, DataSourceInfo> mapCustom = parsePropertiesFile(event
                .getFileName());
        // 把数据源bean注册到容器中
        addSourceBeanToApp(mapCustom);

    }

    /**
     * 功能说明：解析属性文件，得到数据源Map
     *
     * @return
     * @throws IOException
     */
    private Map<String, DataSourceInfo> parsePropertiesFile(String fileName)
            throws IOException {
        // 属性文件读取
        ResourcePropertySource props = new ResourcePropertySource(fileName);
        // 正则匹配解析
        Matcher matcher;
        Pattern pattern = Pattern
                .compile("^link\\.(his2\\.\\w+)\\.jdbc\\.(url|username|password)$");

        Map<String, DataSourceInfo> mapDataSource = new HashMap<String, DataSourceInfo>();
        // 根据配置文件解析数据源
        for (String keyProp : props.getPropertyNames()) {
            matcher = pattern.matcher(keyProp);
            if (matcher.find()) {
                String dsName = matcher.group(1);
                String dsPropName = matcher.group(2);
                DataSourceInfo dsi;

                if (mapDataSource.containsKey(dsName)) {
                    dsi = mapDataSource.get(dsName);
                } else {
                    dsi = new DataSourceInfo();
                }
                // 根据属性名给数据源属性赋值
                if ("url".equals(dsPropName)) {
                    dsi.connUrl = (String) props.getProperty(keyProp);
                } else if ("username".equals(dsPropName)) {
                    dsi.userName = (String) props.getProperty(keyProp);
                } else if ("password".equals(dsPropName)) {
                    dsi.password = (String) props.getProperty(keyProp);
                }
                mapDataSource.put(dsName, dsi);
            }
        }
        return mapDataSource;
    }

    /**
     * 功能说明：根据DataSource创建bean并注册到容器中
     *
     * @param acf
     * @param mapCustom
     */
    private void addSourceBeanToApp(Map<String, DataSourceInfo> mapCustom) {
        DefaultListableBeanFactory acf = (DefaultListableBeanFactory) app
                .getAutowireCapableBeanFactory();
        BeanDefinition beanDefinition;
        Iterator<String> iter = mapCustom.keySet().iterator();
        while (iter.hasNext()) {
            String beanKey = iter.next();
            // 得到Bean定义，并添加到容器中
            beanDefinition = new ChildBeanDefinition(app.getDisplayName());
            // 注意：必须先注册到容器中，再得到Bean进行修改，否则数据源属性不能有效修改
            acf.registerBeanDefinition(beanKey, beanDefinition);
            // 再得到数据源Bean定义，并修改连接相关的属性
            BasicDataSource cpds = (BasicDataSource) app.getBean(beanKey);

            cpds.setUrl(mapCustom.get(beanKey).connUrl);
            cpds.setUsername(mapCustom.get(beanKey).userName);
            cpds.setPassword(mapCustom.get(beanKey).password);
        }
    }

    private class DataSourceInfo {
        public String connUrl;
        public String userName;
        public String password;

        public String toString() {

            return "(JcbcUrl:" + connUrl + ", userName:" + userName
                    + ", password:" + password + ")";
        }
    }

}
